{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "N6ZDpd9XzFeN"
      },
      "source": [
        "##### Copyright 2018 The TensorFlow Hub Authors.\n",
        "\n",
        "Licensed under the Apache License, Version 2.0 (the \"License\");"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "KUu4vOt5zI9d"
      },
      "outputs": [],
      "source": [
        "# Copyright 2018 The TensorFlow Hub Authors. All Rights Reserved.\n",
        "#\n",
        "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "#     http://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License.\n",
        "# =============================================================================="
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ok9PfyoQ2rH_"
      },
      "source": [
        "# TF-Hub による簡単なテキスト分類器の構築方法\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Kzz1rsXKNLww"
      },
      "source": [
        "> 注意: このチュートリアルでは、**非推奨**の TensorFlow 1 の機能を使用しています。このタスクの最新アプローチについては、[TensorFlow 2 バージョン](https://www.tensorflow.org/hub/tutorials/tf2_text_classification)をご覧ください。\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MfBg1C5NB3X0"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/hub/tutorials/text_classification_with_tf_hub.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Run in Google Colab</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ja/hub/tutorials/text_classification_with_tf_hub.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub でソースを表示</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AK3mz3JNMW8Y"
      },
      "source": [
        "TensorFlow Hub (TF-Hub) は、機械学習の知識を再利用可能なリソース、特にトレーニング済みの**モジュール**で共有するためのプラットフォームです。このチュートリアルは次の 2 つの主要部分で構成しています。\n",
        "\n",
        "**入門編:** TF-Hub によるテキスト分類器のトレーニング\n",
        "\n",
        "TF-Hub のテキスト埋め込みモジュールを使用して、適切なベースライン精度を持つ単純な感情分類器をトレーニングします。その後、予測を分析してモデルが適切であるかを確認し、精度を向上させるための改善点を提案します。\n",
        "\n",
        "**上級編:** 転移学習の分析\n",
        "\n",
        "この項目では、様々な TF-Hub モジュールを使用して Estimator の精度への効果を比較し、転移学習のメリットとデメリットを実証します。\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aYVd26q1_3xW"
      },
      "source": [
        "## オプションの前提条件\n",
        "\n",
        "- Tensorflow の[既製の Estimator フレームワーク](https://www.tensorflow.org/get_started/premade_estimators)に対する基礎知識があること。\n",
        "- [Pandas](https://pandas.pydata.org/) ライブラリを熟知していること。\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xOATihhH1IxS"
      },
      "source": [
        "## セットアップ"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "code",
        "id": "_8N3Hx2dyUC-"
      },
      "outputs": [],
      "source": [
        "# Install TF-Hub.\n",
        "!pip install seaborn"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tRXN9a8Mz8e-"
      },
      "source": [
        "Tensorflow のインストールに関する詳細は、[https://www.tensorflow.org/install/](https://www.tensorflow.org/install/) をご覧ください。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "v7hy0bhngTUp"
      },
      "outputs": [],
      "source": [
        "from absl import logging\n",
        "\n",
        "import tensorflow as tf\n",
        "import tensorflow_hub as hub\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import os\n",
        "import pandas as pd\n",
        "import re\n",
        "import seaborn as sns"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6OPyVxHuiTEE"
      },
      "source": [
        "# はじめに\n",
        "\n",
        "## データ\n",
        "\n",
        "[映画レビューの大規模データセット v1.0](http://ai.stanford.edu/~amaas/data/sentiment/) タスク [(Mass et al., 2011)](http://ai.stanford.edu/~amaas/papers/wvSent_acl2011.pdf) を解決してみます。データセットは、1 から 10 までの肯定度でラベル付けされた IMDB 映画レビューで構成されています。タスクは、レビューを**ネガティブ (negative)** または**ポジティブ (positive)** にラベル付けすることです。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "both",
        "id": "rKzc-fOGV72G"
      },
      "outputs": [],
      "source": [
        "# Load all files from a directory in a DataFrame.\n",
        "def load_directory_data(directory):\n",
        "  data = {}\n",
        "  data[\"sentence\"] = []\n",
        "  data[\"sentiment\"] = []\n",
        "  for file_path in os.listdir(directory):\n",
        "    with tf.io.gfile.GFile(os.path.join(directory, file_path), \"r\") as f:\n",
        "      data[\"sentence\"].append(f.read())\n",
        "      data[\"sentiment\"].append(re.match(\"\\d+_(\\d+)\\.txt\", file_path).group(1))\n",
        "  return pd.DataFrame.from_dict(data)\n",
        "\n",
        "# Merge positive and negative examples, add a polarity column and shuffle.\n",
        "def load_dataset(directory):\n",
        "  pos_df = load_directory_data(os.path.join(directory, \"pos\"))\n",
        "  neg_df = load_directory_data(os.path.join(directory, \"neg\"))\n",
        "  pos_df[\"polarity\"] = 1\n",
        "  neg_df[\"polarity\"] = 0\n",
        "  return pd.concat([pos_df, neg_df]).sample(frac=1).reset_index(drop=True)\n",
        "\n",
        "# Download and process the dataset files.\n",
        "def download_and_load_datasets(force_download=False):\n",
        "  dataset = tf.keras.utils.get_file(\n",
        "      fname=\"aclImdb.tar.gz\", \n",
        "      origin=\"http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz\", \n",
        "      extract=True)\n",
        "  \n",
        "  train_df = load_dataset(os.path.join(os.path.dirname(dataset), \n",
        "                                       \"aclImdb\", \"train\"))\n",
        "  test_df = load_dataset(os.path.join(os.path.dirname(dataset), \n",
        "                                      \"aclImdb\", \"test\"))\n",
        "  \n",
        "  return train_df, test_df\n",
        "\n",
        "# Reduce logging output.\n",
        "logging.set_verbosity(logging.ERROR)\n",
        "\n",
        "train_df, test_df = download_and_load_datasets()\n",
        "train_df.head()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "D9Xq4x1mU3un"
      },
      "source": [
        "## モデル\n",
        "\n",
        "### 入力関数\n",
        "\n",
        "[Estimator フレームワーク](https://www.tensorflow.org/get_started/premade_estimators#overview_of_programming_with_estimators)は、Pandasの データフレームをラップする[入力関数](https://www.tensorflow.org/api_docs/python/tf/compat/v1/estimator/inputs/pandas_input_fn)を提供します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "both",
        "id": "25rdoEHih0fm"
      },
      "outputs": [],
      "source": [
        "# Training input on the whole training set with no limit on training epochs.\n",
        "train_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(\n",
        "    train_df, train_df[\"polarity\"], num_epochs=None, shuffle=True)\n",
        "\n",
        "# Prediction on the whole training set.\n",
        "predict_train_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(\n",
        "    train_df, train_df[\"polarity\"], shuffle=False)\n",
        "# Prediction on the test set.\n",
        "predict_test_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(\n",
        "    test_df, test_df[\"polarity\"], shuffle=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Uyl6YGRcVAwP"
      },
      "source": [
        "### 特徴量カラム\n",
        "\n",
        "TF-Hub は、特定のテキスト特徴量にモジュールを適用し、モジュールの出力をさらに渡す、[特徴量カラム](https://www.tensorflow.org/hub/api_docs/python/hub/text_embedding_column.md)を提供しています。このチュートリアルでは [nlm-en-dim128 モジュール](https://tfhub.dev/google/nnlm-en-dim128/1)を使用します。このチュートリアルにおいて、最も重要な事実は次の通りです。\n",
        "\n",
        "- このモジュールは、**文のバッチを1 次元のテンソル文字列で**入力として受け取ります。\n",
        "- このモジュールは、**文の前処理**（例えば、句読点の削除やスペースの分割など）を担当します。\n",
        "- このモジュールは任意の入力に使用できます（例えば **nlm-en-dim128** は、語彙に存在していない単語を 20.000 バケットまでハッシュします）。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "X7vyvj-hDEXu"
      },
      "outputs": [],
      "source": [
        "embedded_text_feature_column = hub.text_embedding_column(\n",
        "    key=\"sentence\", \n",
        "    module_spec=\"https://tfhub.dev/google/nnlm-en-dim128/1\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YPuHgx3BWBOg"
      },
      "source": [
        "### Estimator\n",
        "\n",
        "分類には [DNN 分類器](https://www.tensorflow.org/api_docs/python/tf/estimator/DNNClassifier)を使用することができます。（注: ラベル関数の異なるモデリングに関しては、追加の留意点がチュートリアルの最後にあります。）"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "23U30yEkVq4w"
      },
      "outputs": [],
      "source": [
        "estimator = tf.estimator.DNNClassifier(\n",
        "    hidden_units=[500, 100],\n",
        "    feature_columns=[embedded_text_feature_column],\n",
        "    n_classes=2,\n",
        "    optimizer=tf.keras.optimizers.Adagrad(lr=0.003))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-O_k-8jgWPXY"
      },
      "source": [
        "### トレーニング\n",
        "\n",
        "妥当なステップ数の分だけ、Estimator をトレーニングします。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "e5uDRv1r7Ed4"
      },
      "outputs": [],
      "source": [
        "# Training for 5,000 steps means 640,000 training examples with the default\n",
        "# batch size. This is roughly equivalent to 25 epochs since the training dataset\n",
        "# contains 25,000 examples.\n",
        "estimator.train(input_fn=train_input_fn, steps=5000);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "s8j7YTRSe7Pj"
      },
      "source": [
        "# 予測する\n",
        "\n",
        "トレーニングセットとテストセットの両方で予測を実行します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zbLg5LzGwAfC"
      },
      "outputs": [],
      "source": [
        "train_eval_result = estimator.evaluate(input_fn=predict_train_input_fn)\n",
        "test_eval_result = estimator.evaluate(input_fn=predict_test_input_fn)\n",
        "\n",
        "print(\"Training set accuracy: {accuracy}\".format(**train_eval_result))\n",
        "print(\"Test set accuracy: {accuracy}\".format(**test_eval_result))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DR2IsTF5vuAX"
      },
      "source": [
        "## 混同行列\n",
        "\n",
        "混同行列を目視確認して、誤分類の分布を把握することができます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "nT71CtArpsKz"
      },
      "outputs": [],
      "source": [
        "def get_predictions(estimator, input_fn):\n",
        "  return [x[\"class_ids\"][0] for x in estimator.predict(input_fn=input_fn)]\n",
        "\n",
        "LABELS = [\n",
        "    \"negative\", \"positive\"\n",
        "]\n",
        "\n",
        "# Create a confusion matrix on training data.\n",
        "cm = tf.math.confusion_matrix(train_df[\"polarity\"], \n",
        "                              get_predictions(estimator, predict_train_input_fn))\n",
        "\n",
        "# Normalize the confusion matrix so that each row sums to 1.\n",
        "cm = tf.cast(cm, dtype=tf.float32)\n",
        "cm = cm / tf.math.reduce_sum(cm, axis=1)[:, np.newaxis]\n",
        "\n",
        "sns.heatmap(cm, annot=True, xticklabels=LABELS, yticklabels=LABELS);\n",
        "plt.xlabel(\"Predicted\");\n",
        "plt.ylabel(\"True\");"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sG-ES55Ftp-t"
      },
      "source": [
        "# もっと改善するために\n",
        "\n",
        "1. **感情の回帰**: 極性クラスに各例を割り当てる際には分類器を使用しました。しかし実際には感情という、もう 1 つの自由に使える分類的特徴があります。この場合クラスは実際にはスケールを表し、連続的な範囲に基礎的な値（ポジティブまたはネガティブ）をうまくマッピングすることができます。分類（[DNN 分類器](https://www.tensorflow.org/api_docs/python/tf/contrib/learn/DNNClassifier)）の代わりに回帰（[DNN 回帰器](https://www.tensorflow.org/api_docs/python/tf/contrib/learn/DNNRegressor)）を計算することによって、このプロパティが使用できるようにしました。\n",
        "2. **大規模モジュール**: 本チュートリアルでは、メモリ使用量を制限するために小さなモジュールを使用しました。もっと語彙が多く埋め込み空間が大きいモジュールを使用すると、精度のポイントがさらに向上する可能性があります。\n",
        "3. **パラメータ調整**: 学習率やステップ数などのメタパラメータを調整することにより、精度を向上させることができます。特に異なるモジュールを使用している場合にこれは有効です。妥当な結果を得るためには、検証セットが非常に重要な要素です。なぜなら、テストセットに一般化させないでトレーニングデータの予測を学習するモデルを設定するのは非常に簡単だからです。\n",
        "4. **より複雑なモデル**: 本チュートリアルでは個々の単語を埋め込み、さらに平均値と組み合わせて文の埋め込みを計算するモジュールを使用しました。Sequential モジュール（例えば[ユニバーサルセンテンスエンコーダ](https://tfhub.dev/google/universal-sentence-encoder/2)モジュールなど）を使用して、文の性質をさらに良く捉えることも可能です。あるいは、2 つ以上の TF-Hub モジュールをアンサンブルします。\n",
        "5. **正則化**: 過適合を防ぐために、[Proximal Adagrad オプティマイザ](https://www.tensorflow.org/api_docs/python/tf/compat/v1/train/ProximalAdagradOptimizer)などの正則化を行うオプティマイザを使用してみるのもよいでしょう。\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fKRNsaO8L50F"
      },
      "source": [
        "# 上級編: 転移学習の分析\n",
        "\n",
        "転移学習によって、**トレーニングリソースが節約され**、<strong>小さなデータセットによるトレーニング</strong>でも良好なモデルの一般化が実現できるようになりました。この項目では、2 つの異なる TF-Hub モジュールを使用してトレーニングを行い、これを実証します。\n",
        "\n",
        "- **[nnlm-en-dim128](https://tfhub.dev/google/nnlm-en-dim128/1)** - 事前トレーニング済みのテキスト埋め込みモジュール\n",
        "- **[random-nnlm-en-dim128](https://tfhub.dev/google/random-nnlm-en-dim128/1)** - **nlm-en-dim128** と同じ語彙とネットワークを持ちますが、重みはランダムに初期化され、実際のデータではトレーニングされていない、テキスト埋め込みモジュール\n",
        "\n",
        "これを 2 つのモードでトレーニングします。\n",
        "\n",
        "- <strong>分類器のみ</strong>をトレーニングする（つまりモジュールは凍結） 。\n",
        "- **モジュールと分類器を一緒に**トレーニングする。\n",
        "\n",
        "様々なモジュールを使用して複数のトレーニングと評価を行い、精度にどのような影響が出るかを見てみましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "AWYa1So1ARyz"
      },
      "outputs": [],
      "source": [
        "def train_and_evaluate_with_module(hub_module, train_module=False):\n",
        "  embedded_text_feature_column = hub.text_embedding_column(\n",
        "      key=\"sentence\", module_spec=hub_module, trainable=train_module)\n",
        "\n",
        "  estimator = tf.estimator.DNNClassifier(\n",
        "      hidden_units=[500, 100],\n",
        "      feature_columns=[embedded_text_feature_column],\n",
        "      n_classes=2,\n",
        "      optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.003))\n",
        "\n",
        "  estimator.train(input_fn=train_input_fn, steps=1000)\n",
        "\n",
        "  train_eval_result = estimator.evaluate(input_fn=predict_train_input_fn)\n",
        "  test_eval_result = estimator.evaluate(input_fn=predict_test_input_fn)\n",
        "\n",
        "  training_set_accuracy = train_eval_result[\"accuracy\"]\n",
        "  test_set_accuracy = test_eval_result[\"accuracy\"]\n",
        "\n",
        "  return {\n",
        "      \"Training accuracy\": training_set_accuracy,\n",
        "      \"Test accuracy\": test_set_accuracy\n",
        "  }\n",
        "\n",
        "\n",
        "results = {}\n",
        "results[\"nnlm-en-dim128\"] = train_and_evaluate_with_module(\n",
        "    \"https://tfhub.dev/google/nnlm-en-dim128/1\")\n",
        "results[\"nnlm-en-dim128-with-module-training\"] = train_and_evaluate_with_module(\n",
        "    \"https://tfhub.dev/google/nnlm-en-dim128/1\", True)\n",
        "results[\"random-nnlm-en-dim128\"] = train_and_evaluate_with_module(\n",
        "    \"https://tfhub.dev/google/random-nnlm-en-dim128/1\")\n",
        "results[\"random-nnlm-en-dim128-with-module-training\"] = train_and_evaluate_with_module(\n",
        "    \"https://tfhub.dev/google/random-nnlm-en-dim128/1\", True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CsWppYMphIPh"
      },
      "source": [
        "結果を見てみましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "UVkdErEKkIXL"
      },
      "outputs": [],
      "source": [
        "pd.DataFrame.from_dict(results, orient=\"index\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Z9rZ2fuGfUFh"
      },
      "source": [
        "既に複数のパターンが見られますが、まず最初にテストセットのベースラインの精度、つまり最も代表的なクラスのラベルのみを出力して達成可能な下限値を確立させる必要があります。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IgYPVvc3G6OS"
      },
      "outputs": [],
      "source": [
        "estimator.evaluate(input_fn=predict_test_input_fn)[\"accuracy_baseline\"]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UN4D-DPPrINX"
      },
      "source": [
        "最も代表的なクラスを割り当てると、精度は **50%** になります。ここで注目すべき点がいくつかあります。\n",
        "\n",
        "1. 驚かれるかも知れませんが、**モデルは固定されたランダムな埋め込み上でまだ学習することが可能です**。その理由は、ディクショナリのすべての単語がランダムなベクトルにマップされていたとしても、Estimator は完全に接続されたレイヤーを使用するだけで空間を分離することができるからです。\n",
        "2. **ランダム埋め込み**を使用したモジュールのトレーニングを許可すると、分類器だけをトレーニングする場合に比べ、トレーニングとテスト両方の精度が向上します。\n",
        "3. また、**事前トレーニング済みの埋め込み**でモジュールをトレーニングすると、トレーニングとテスト両方の精度が向上します。ただし、トレーニングセットの過適合には注意してください。事前トレーニング済みのモジュールをトレーニングすることは、正則化を行っても埋め込みの重みが多様なデータでトレーニングされた言語モデルを表現することができなくなるという意味で危険です。その代わりに収束して新しいデータセットを理想的な表現にします。"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [
        "N6ZDpd9XzFeN"
      ],
      "name": "text_classification_with_tf_hub.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
